package service

import (
	"context"
	"go.uber.org/zap"
	"platon-tools/go-service/event"
	logger "platon-tools/go-service/go-logger"
	"sync"
	"sync/atomic"
)

type Service struct {
	config *Config

	modules struct {
		m map[string]Module
		sync.RWMutex
	}

	running int32

	ctx    context.Context
	cancel context.CancelFunc

	feed event.Event
}

func NewService(ctx context.Context, config *Config) *Service {
	self, cancel := context.WithCancel(ctx)
	service := &Service{
		config: config,
		ctx:    self,
		cancel: cancel,
	}

	service.modules.m = make(map[string]Module)

	atomic.StoreInt32(&service.running, 1)
	return service
}

func (s *Service) RegisterModule(modules ...Module) {
	if atomic.LoadInt32(&s.running) == 1 {
		s.modules.Lock()
		defer s.modules.Unlock()

		for i, m := range modules {
			if _, ok := s.modules.m[m.Name()]; !ok {
				s.modules.m[m.Name()] = modules[i]
			}
		}
	}
}

func (s *Service) Start() error {
	setup := func(n string, m Module) error {
		if err := m.Start(s.ctx, s.config, s); err != nil {
			logger.Logger.Error("setup module failed", zap.String("name", n), zap.String("error", err.Error()))
			return err
		}

		return nil
	}

	s.modules.Lock()
	defer s.modules.Unlock()
	for name, module := range s.modules.m {
		if err := setup(name, module); err != nil {
			return err
		}
	}

	return nil
}

func (s *Service) ModuleByName(name string) Module {
	s.modules.RLock()
	defer s.modules.RLocker()

	if m, ok := s.modules.m[name]; ok {
		return m
	}

	return nil
}

func (s *Service) Close() error {
	if atomic.LoadInt32(&s.running) == 1 {
		atomic.StoreInt32(&s.running, 0)
		s.modules.Lock()
		for _, m := range s.modules.m {
			m.Close()
		}
		s.modules.Unlock()
		logger.Logger.Sync()
		s.cancel()
	}
	return nil
}

func (s *Service) Subscribe(channel interface{}) event.Subscription {
	if atomic.LoadInt32(&s.running) == 1 {
		return s.feed.Subscribe(channel)
	}

	return nil
}

func (s *Service) GetEvent() *event.Event {
	return &s.feed
}
